package com.ys168.gam.shortestpath.view;

import static com.ys168.gam.shortestpath.message.Messages.getString;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JPanel;

import com.ys168.gam.shortestpath.message.MessagesCode;
import com.ys168.gam.shortestpath.model.Node;
import com.ys168.gam.shortestpath.util.DrawALUtil;
import com.ys168.gam.shortestpath.util.MoveUtil;

/**
 *
 * @author Jason Krebs
 * @date 2015年2月2日
 */
public class CanvasPanel extends JPanel {

    public interface IRunningFinishedListener {
        void onRunningFinished();
    }

    static final Image NODE_IMAGE = Toolkit.getDefaultToolkit().getImage(CanvasPanel.class.getResource("/image/CrystalCircle.png"));

    private static final Image RUNNER = Toolkit.getDefaultToolkit().getImage(CanvasPanel.class.getResource("/image/Runner.png"));
    private static final long serialVersionUID = 6306635296207278333L;
    private static final Logger log = Logger.getLogger(CanvasPanel.class.getName());

    private List<Node> nodes;

    private Node focusedNode;
    private Node player;
    private Node startNode;
    private Node endNode;

    private Point2D tempPoint;

    private boolean directedGraph;
    private boolean showWeight;
    private boolean isRunning;

    private IRunningFinishedListener runningFinishedListener;

    public CanvasPanel() {
        showWeight = false;
        directedGraph = false;
        nodes = new ArrayList<Node>();
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (e.getButton() == MouseEvent.BUTTON1) {
                    if (tempPoint == null || getFocusedNode() == null) {
                        return;
                    }
                    Node node = getNode(tempPoint);
                    if (node != null) {
                        node.addInNode(getFocusedNode());
                        getFocusedNode().addOutNode(node);
                    }
                }
                if (tempPoint != null) {
                    tempPoint = null;
                }
            }
        });
        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                generateLine(e.getPoint());
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                Node beforeFocusedNode = focusedNode;
                focusedNode = getNode(e.getPoint());
                if (beforeFocusedNode != focusedNode) {
                    repaint();
                }
            }
        });
    }

    public void cleanAll() {
        focusedNode = null;
        startNode = null;
        endNode = null;
        player = null;
        tempPoint = null;
        nodes.clear();
        repaint();
    }

    public void cleanLines() {
        for (Node node : nodes) {
            node.cleanRelations();
        }
        repaint();
    }

    private void drawLine(Graphics2D g2) {
        g2.setColor(Color.GREEN);
        for (Node node : nodes) {
            for (Node endNode : node.getOutNodes()) {
                Point2D a = node.getLocation();
                Point2D b = endNode.getLocation();
                drawLine(a, b, g2);
                if (showWeight) {
                    g2.drawString(String.format("%.2f", node.calculateLength(endNode)), (float) (a.getX() + b.getX()) / 2, (float) (a.getY() + b.getY()) / 2);
                }
            }
        }
        if (tempPoint != null && getFocusedNode() != null) {
            Node node = getNode(tempPoint);
            g2.setColor(node == null ? Color.RED : Color.YELLOW);
            drawLine(getFocusedNode().getLocation(), tempPoint, g2);
        }
    }

    private void drawLine(Point2D a, Point2D b, Graphics2D g2) {
        if (directedGraph) {
            DrawALUtil.drawAL(a, b, g2);
        }
        else {
            g2.draw(new Line2D.Double(a, b));
        }
    }

    private void drawNode(Graphics2D g2d) {
        for (Node node : nodes) {
            g2d.setColor(Color.WHITE);
            node.draw(g2d);

            if (node.equals(startNode)) {
                g2d.setColor(Color.CYAN);
                g2d.draw(node.getShape());
            }
            if (node.equals(endNode)) {
                g2d.setColor(Color.YELLOW);
                g2d.draw(node.getShape());
            }
            if (node.equals(focusedNode)) {
                g2d.setColor(Color.WHITE);
                g2d.draw(node.getShape());
            }
        }
    }

    private void drawPlayer(Graphics2D g2d) {
        if (player != null) {
            player.draw(g2d);
        }
    }

    public void exchange() {
        Node tempNode = startNode;
        startNode = endNode;
        endNode = tempNode;
        repaint();
    }

    /**
     * 生成直线
     */
    private boolean generateLine(Point2D point) {
        if (getFocusedNode() == null) {
            return false;
        }
        Node node = getNode(point);
        if (node == null || getFocusedNode().equals(node)) {
            tempPoint = point;
            repaint();
            return false;
        }
        tempPoint = node.getLocation();
        repaint();

        return true;
    }

    public void generateNode(Point2D point) {
        Node node = new Node(point, NODE_IMAGE);
        node.setIndex(getNextNodeIndex());
        nodes.add(node);
        repaint();
    }

    public List<Node> getAllNodes() {
        return Collections.unmodifiableList(nodes);
    }

    public Node getEndNode() {
        return endNode;
    }

    public Node getFocusedNode() {
        return focusedNode;
    }

    public int getLineCount() {
        int count = 0;
        for (Node node : nodes) {
            count += node.getOutNodes().size();
        }
        return count;
    }

    /**
     * 生成下一个节点的标识ID
     */
    private int getNextNodeIndex() {
        return nodes.isEmpty() ? 1 : nodes.get(nodes.size() - 1).getIndex() + 1;
    }

    /**
     * 根据位置获取节点，如果没找到则返回null
     */
    public Node getNode(Point2D point) {
        if (point == null) {
            return null;
        }
        for (Node node : nodes) {
            if (node.isInclude(point)) {
                return node;
            }
        }
        return null;
    }

    public int getNodeCount() {
        return nodes.size();
    }

    public Node getStartNode() {
        return startNode;
    }

    // @formatter:off
	public boolean isRunnable() {
		if (isRunning) {
			return false;
		}
		if (startNode == null || endNode == null || startNode == endNode) {
			return false;
		}
		return getLineCount() > 0;
	}

	public boolean isShowWeight() {
		return showWeight;
	}

	// @formatter:on

    public void loadNodes(List<Node> nodes) {
        this.nodes.clear();
        this.nodes.addAll(nodes);
        repaint();
    }

    /**
     * 移动节点
     *
     * @param point
     * @return
     */
    public void moveNode(Point2D point) {
        if (getFocusedNode() != null) {
            getFocusedNode().setLocation(point.getX(), point.getY());
            if (player != null && getFocusedNode().getIndex() == player.getIndex()) {
                player.setLocation(point);
            }
            repaint();
        }
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        drawNode((Graphics2D) g);
        drawLine((Graphics2D) g);
        drawPlayer((Graphics2D) g);
    }

    public boolean removeLine() {
        Node node = getFocusedNode();

        if (node == null) {
            return false;
        }
        for (Node in : node.getInNodes()) {
            in.removeNode(node);
        }
        for (Node out : node.getOutNodes()) {
            out.removeNode(node);
        }
        node.cleanRelations();
        repaint();

        return true;
    }

    public boolean removeNode() {
        Node node = getFocusedNode();
        if (node == null) {
            return false;
        }
        for (Node n : node.getAllRelations()) {
            n.removeNode(node);
        }
        nodes.remove(node);
        repaint();

        return true;
    }

    private void resetRunner() {
        if (nodes.isEmpty()) {
            return;
        }
        if (player == null) {
            Node node = nodes.get(0);
            player = new Node(node.getLocation(), RUNNER);
        }
        if (startNode != null) {
            player.setLocation(startNode.getLocation());
            player.setIndex(startNode.getIndex());
        }
    }

    public void run(final List<Node> path) {
        isRunning = true;

        if (path.isEmpty()) {
            log.log(Level.WARNING, getString(MessagesCode.LOG_02));
            stop();
            return;
        }
        resetRunner();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (Node node : path) {
                    if (!isRunning) {
                        break;
                    }
                    player.setIndex(node.getIndex());
                    Point2D source = player.getLocation();
                    Point2D target = node.getLocation();
                    while (!node.getLocation().equals(player.getLocation())) {
                        Point2D nextLocation = MoveUtil.getNextLocation(player.getLocation(), source, target);
                        player.setLocation(nextLocation);
                        repaint();
                        try {
                            Thread.sleep(17);
                        }
                        catch (InterruptedException e) {
                            log.log(Level.WARNING, e.getMessage());
                        }
                    }
                }
                stop();
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

    public void setDirectedGraph(boolean isDirectedGraph) {
        this.directedGraph = isDirectedGraph;
    }

    public void setEnd() {
        this.endNode = focusedNode;
        if (this.startNode == focusedNode) {
            this.startNode = null;
        }
    }

    public void setOnRunningFinshed(IRunningFinishedListener runningFinishedListener) {
        this.runningFinishedListener = runningFinishedListener;
    }

    public void setRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }

    public void setShowWeight(boolean showWeight) {
        this.showWeight = showWeight;
    }

    public void setStart() {
        this.startNode = focusedNode;
        if (this.endNode == focusedNode) {
            this.endNode = null;
        }
    }

    /**
     * 停止当前正在运行的
     */
    public void stop() {
        if (isRunning) {
            isRunning = false;
            if (runningFinishedListener != null) {
                runningFinishedListener.onRunningFinished();
            }
        }
    }

}
